package analysis;

import java.util.LinkedList;

import nodes.Node;
import nodes.NodeContainer;
import drivers.Config;

public class Analyzer {
	
	private NodeContainer container;

	private NodeContainer subsetContainer;
	
	private Node focusNode;
	
	//private ArrayList<Node> alreadyAnalyzed;
	
	private String focusIPAddress;
	
	private int maxNodesTotal;
	private int maxNumUltrapeers;
	private int maxNumPeers;
	private int maxNumEdges;
	
	private int countTotal = 0;
	private int countUltrapeers = 0;
	private int countPeers = 0;
	private int countEdges = 0;
	
	public Analyzer(Config config, NodeContainer container) {
		this.subsetContainer = new NodeContainer();
		
		//this.alreadyAnalyzed = new ArrayList<Node>();
		
		this.container = container;
		
		this.focusIPAddress = config.getFocusIPAddress();
				
		this.maxNodesTotal = (config.getMaxNodesTotal() == -1) ? Integer.MAX_VALUE : config.getMaxNodesTotal();
		this.maxNumUltrapeers = (config.getMaxNumUltrapeers() == -1) ? Integer.MAX_VALUE : config.getMaxNumUltrapeers();
		this.maxNumPeers = (config.getMaxNumPeers() == -1) ? Integer.MAX_VALUE : config.getMaxNumPeers();
		this.maxNumEdges = (config.getMaxNumEdges() == -1) ? Integer.MAX_VALUE : config.getMaxNumEdges();
	}
	
	public NodeContainer getSubsetNodeContainer() {
		return this.subsetContainer;
	}
	
	public Node getFocusNode() {
		return this.focusNode;
	}
	
	/**
	 * Selects a subset from the {@link #container} passed in during construction.
	 * 
	 * Uses the focus IP specified in config if one exists, otherwise it picks a random UltraPeer from {@link #container}.
	 */
	public void selectSubset() {
		Node focusNode = null;
		if(!this.focusIPAddress.equals("")) {
			focusNode = container.get(this.focusIPAddress);
		}
		
		if(focusNode == null) {
			focusNode = container.getRandomNode(Node.NODE_TYPE_ULTRAPEER);
		}
		
		if(focusNode != null) {
			/*
			this.subsetContainer.addNode(focusNode);
			// Increment counts
			this.incrementCounts(focusNode.getType());
			// Recursively call the addChildren method until one of the limits has been reached.
			addNodeChildren(focusNode);
			*/
			this.addNodeChildrenIterative(focusNode);
			this.focusNode = focusNode;
		} else {
			System.err.println("ERROR: Unable to select a focus node based on criteria!");
			System.exit(2);
		}
	}
	
	/**
	 * Uses a breadth first search to traverse the NodeContainer until one of the maximums in the config class are reached.
	 * 
	 * @param focusNode		A node to start traversal on
	 */
	private void addNodeChildrenIterative(Node focusNode) {
		LinkedList<Node> nodeQueue = new LinkedList<Node>();
		
		this.subsetContainer.addNode(focusNode);
		nodeQueue.add(focusNode);
		
		boolean stopAnalyzing = false;
		while(!stopAnalyzing) {
			// Get the next node in the queue
			Node curNode = nodeQueue.removeFirst();
			
			// Loop through all the children and add them to the queue if allowed
			for (Node curChild : curNode.getConnections()) {
				// Add the node if it doesn't violate any maximums
				if(this.countTotal <= this.maxNodesTotal && this.countUltrapeers <= this.maxNumUltrapeers && this.countPeers <= this.maxNumPeers && this.countEdges <= this.maxNumEdges) {
					// If the node isn't already contained in this subset
					if(!this.subsetContainer.containsValue(curChild)) {
						this.subsetContainer.addNode(curChild);
						// Increment counts
						this.incrementCounts(curChild.getType());
						// Add this node to the queue
						nodeQueue.add(curChild);
					}
					// The condition for adding an edge to the edge count
					//	is whether or not the current child node has already been
					//	analyzed or not. It is NOT only dependent on whether or not
					//	the current child node is the subset.
					if(nodeQueue.contains(curChild)) {
						this.countEdges++;
					}
				} else {
					stopAnalyzing = true;
				}
			}
		}
	}
	
	/*
	private void addNodeChildren(Node node) {
		ArrayList<Node> nodesToRecurse = new ArrayList<Node>();
		
		// Loop through all the children and add them if allowed
		for (Node curNode : node.getConnections()) {
			// Add the node if it doesn't violate any maximums
			if(this.countTotal <= this.maxNodesTotal &&
					this.countUltrapeers <= this.maxNumUltrapeers &&
					this.countPeers <= this.maxNumPeers &&
					this.countEdges <= this.maxNumEdges) {
				// If the node isn't already contained in this subset
				if(!this.subsetContainer.containsValue(curNode)) {
					this.subsetContainer.addNode(curNode);
					// Increment counts
					this.incrementCounts(curNode.getType());
					// Mark this node to be recursed
					nodesToRecurse.add(curNode);
				}
				// The condition for adding an edge to the edge count
				//	is whether or not the current child node has already been
				//	analyzed or not. It is NOT only dependent on whether or not
				//	the current child node is the subset.
				if(!this.alreadyAnalyzed.contains(curNode)) {
					this.countEdges++;
				}
			}
		}
		
		// Loop through all the children and recursively call this function on them as well
		for (Node curNode2 : nodesToRecurse) {
			addNodeChildren(curNode2);
		}	
	}
	*/
	
	/**
	 * Increases total node count and the count for a specific type of node.
	 * 
	 * @param type	type of node to increase counts for
	 */
	private void incrementCounts(int type) {
		// Increment total count
		this.countTotal++;

		// Increment each count per type
		if(type == Node.NODE_TYPE_ULTRAPEER) {
			this.countUltrapeers++;
		} else if(type == Node.NODE_TYPE_PEER) {
			this.countPeers++;
		}
	}
}
